package com.px.user.service;

import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.px.common.core.RespCode;
import com.px.common.core.RespException;
import com.px.common.util.FastUtil;
import com.px.common.util.JsonUtil;
import com.px.common.util.SmsUtil;
import com.px.user.entity.*;
import com.px.user.mapper.ConstTMapper;
import com.px.user.mapper.RedPackMapper;
import com.px.user.mapper.SmsMapper;
import com.px.user.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.px.common.security.AuthConst.REDIS_TOKEN_EXPIRE;

@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedPackMapper redPackMapper;

    @Autowired
    private SmsRepository smsRepository;

    @Autowired
    private SmsMapper smsMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ConstTMapper constTMapper;
    private final String CONSTT_ROBOT = "Robot-RedPack";

    @Value("${px.random-pack.rp-expire}")
    private String rpExpire;

    @Value("${px.random-pack.rp-user}")
    private String rpUser;

    @Value("${px.random-pack.rp-list}")
    private String rpList;

    @Value("${px.notify.redpack-result}")
    private String redpackResult;

    @Value("${px.notify.redpack-result-delay}")
    private String redpackResultDelay;

    public boolean isMoneyGte(Long userid, BigDecimal money) {
        if (money == null)
            return false;
        User user = userMapper.findById(userid);
        return user.getMoney() != null && user.getMoney().compareTo(money) >= 0;
    }

    public void addMoney(Long userId, BigDecimal money) {
        userMapper.addMoney(User.builder().id(userId).money(money).build());
    }

    public void subMoney(Long userId, BigDecimal money) {
        userMapper.addMoney(User.builder().id(userId).money(money.negate()).build());
    }

    /**
     * 发红包 - {@link RedPack#getMoney()} 红包金额最小单位暂时只支持保留一位小数。
     *
     * @param userId  发红包用户ID
     * @param redPack 红包信息
     */
    @Transactional
    public Long sendRp(Long userId, RedPack redPack) {
        log.debug("send red pack: " + userId + ", " + redPack);
        redPack.setMoney(redPack.getMoney().setScale(1, BigDecimal.ROUND_DOWN));
        if (!isMoneyGte(userId, redPack.getMoney()))
            throw new RespException(RespCode.NOT_ENOUGH_MONEY, null);

        subMoney(userId, redPack.getMoney());
        redPackMapper.insert(redPack);  //插入红包数据
        List<String> rrp = FastUtil.randomRp(redPack.getMoney(), redPack.getAmount());  //生成指定数量的红包

        double expire=System.currentTimeMillis() / 1000 + TimeUnit.HOURS.toSeconds(4); //红包过期时间
        redisTemplate.opsForZSet().add(rpExpire, userId + "-" + redPack.getId() + "-" + redPack.getRoomId(),expire);
        redisTemplate.opsForList().leftPushAll("rp-" + redPack.getId(), rrp);

        grabRpByRobot(redPack.getRoomId(), userId, redPack.getId(), redPack.getMoney(), redPack.getAmount());

        return redPack.getId();
    }

    public List<RedPack> roomRpList(Long roomId) {
        List<RedPack> redPacks = new ArrayList<>();

        Cursor rp = redisTemplate.opsForZSet().scan(rpExpire, ScanOptions.scanOptions().match("*-" + roomId).build());
        while (rp.hasNext()) {
            ZSetOperations.TypedTuple typedTuple = (ZSetOperations.TypedTuple) rp.next();
            String val = (String) typedTuple.getValue();
            if (val == null || typedTuple.getScore() == null)
                continue;
            String[] vals = val.split("-");
            redPacks.add(RedPack.builder()
                    .id(Long.valueOf(vals[1]))
                    .creatorId(Long.valueOf(vals[0]))
                    .roomId(Long.valueOf(vals[2]))
                    .createTime(new Timestamp(typedTuple.getScore().longValue() * 1000))
                    .build());
        }
        return redPacks;
    }

    @Transactional
    public void addRpMoney(RedPackGrab redPackGrab) {
        redPackMapper.insertGrab(redPackGrab);
        userMapper.addMoney(User.builder().id(redPackGrab.getToUserId()).money(redPackGrab.getMoney()).build());
    }

    /**
     * 机器人抢红包
     * @param roomId        房间id
     * @param fromUserId    发红包id
     * @param redPackId          红包id
     * @param money         红包总金额
     * @param totalCount   红包总数量
     */
    public void grabRpByRobot(Long roomId, Long fromUserId, Long redPackId, BigDecimal money, Integer totalCount) {
        ConstT constT = constTMapper.findByCode(CONSTT_ROBOT);
        if (constT == null) return;

        float threshold = Float.parseFloat(constT.getValue());  //获取红包比例
        if (threshold > 1 || threshold <= 0) return;

        double accGrabMoney = 0;  //当前已派发红包总金额
        double grabThreshold = money.floatValue() * threshold;   //机器人可抢红包总金额
        int robitCount=(int)(totalCount*threshold);
        List<User> robotIds = userMapper.findRandomRobot(robitCount); //随机指定红包个数的机器人
        if(robotIds==null || robotIds.size()<=0) return;

        User fromUser=userMapper.findById(fromUserId);
        for (User user : robotIds) {
            if (accGrabMoney >= grabThreshold) break;

            //机器人开始抢红包
            BigDecimal grabMoney = grabRpByRobot(roomId, fromUserId, user.getId(), redPackId);
            if (grabMoney == null) continue;

            // TODO 机器人抢到红包延时通知
            redisTemplate.opsForList().leftPush(redpackResultDelay+'-'+redPackId, Objects.requireNonNull(JsonUtil.toJson(ImmutableMap.of(
                    "roomId", roomId.toString(), "money", grabMoney.toString(), "toUserId", user.getId().toString(), "rpid",redPackId.toString(),"nickname",fromUser.getNickname()))));

            accGrabMoney += grabMoney.doubleValue();
        }
    }

    /**
     * 机器人抢红包
     *
     * @param roomId     房间ID
     * @param fromUserId 发包ID
     * @param toUserId   收包ID
     * @param rpid       红包ID
     * @return
     */
    public BigDecimal grabRpByRobot(Long roomId, Long fromUserId, Long toUserId, Long rpid) {
        Boolean alreadyGrab = redisTemplate.opsForSet().isMember(rpUser + rpid, String.valueOf(toUserId));
        if (alreadyGrab != null && alreadyGrab)   //当前用户已经抢过红包
            throw new RespException(RespCode.RP_NO_REPEAT, null);

        String rp = String.join("-", String.valueOf(fromUserId), String.valueOf(rpid), String.valueOf(roomId));
        Double preTime = redisTemplate.opsForZSet().score(rpExpire, rp);
        if (preTime == null || preTime < new Date().getTime() / 1000) {
            redisTemplate.opsForZSet().remove(rpExpire, rp);
            redisTemplate.delete(rpList + rpid);
            redisTemplate.delete(rpUser + rpid);
            throw new RespException(RespCode.RP_INVALID, null);
        }

        String money = redisTemplate.opsForList().rightPop(rpList + rpid);
        if (money == null) {
            throw new RespException(RespCode.RP_DONE, null);
        }

        BigDecimal moneyDecimal = BigDecimal.valueOf(Double.valueOf(money));
        addRpMoney(RedPackGrab.builder().redPackId(rpid).toUserId(toUserId).money(moneyDecimal).build());
        redisTemplate.opsForSet().add(rpUser + rpid, String.valueOf(toUserId));
        return moneyDecimal;
    }

    public static Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        return new org.apache.commons.beanutils.BeanMap(obj);
    }

    /**
     * 抢红包
     *
     * @param roomId     房间ID
     * @param fromUserId 发包ID
     * @param toUserId   收包ID
     * @param rpid       红包ID
     * @return
     */
    public BigDecimal grabRp(Long roomId, Long fromUserId, Long toUserId, Long rpid)
    {
        String popValue=null;
        do{
            popValue = redisTemplate.opsForList().leftPop(redpackResultDelay+'-'+rpid);
            if(popValue==null) break;
            //log.info("popValue:"+popValue.toString());
            JsonObject jsonObj = new JsonParser().parse(popValue).getAsJsonObject();
//            log.info("robot message:"+
//                    "roomId"+ jsonObj.get("roomId").getAsString()+"money"+ jsonObj.get("money").getAsString()+ "toUserId"+ jsonObj.get("toUserId").getAsString()+
//                    "rpid"+jsonObj.get("rpid").getAsString()+"nickname"+jsonObj.get("nickname").getAsString());
            redisTemplate.opsForList().leftPush(redpackResult, Objects.requireNonNull(JsonUtil.toJson(ImmutableMap.of(
                "roomId", jsonObj.get("roomId").getAsString(), "money", jsonObj.get("money").getAsString(), "toUserId", jsonObj.get("toUserId").getAsString(),
                "rpid",jsonObj.get("rpid").getAsString(),"nickname",jsonObj.get("nickname").getAsString()))));
        }while (popValue!=null);

        Boolean alreadyGrab = redisTemplate.opsForSet().isMember(rpUser + rpid, String.valueOf(toUserId));
        if (alreadyGrab != null && alreadyGrab)   //当前用户已经抢过红包
            throw new RespException(RespCode.RP_NO_REPEAT, null);

        String rp = String.join("-", String.valueOf(fromUserId), String.valueOf(rpid), String.valueOf(roomId));
        Double preTime = redisTemplate.opsForZSet().score(rpExpire, rp);
        if (preTime == null || preTime < new Date().getTime() / 1000) {
            redisTemplate.opsForZSet().remove(rpExpire, rp);
            redisTemplate.delete(rpList + rpid);
            redisTemplate.delete(rpUser + rpid);
            throw new RespException(RespCode.RP_INVALID, null);
        }

        String money = redisTemplate.opsForList().rightPop(rpList + rpid);
        if (money == null) {
             /* redisTemplate.opsForZSet().remove(rpExpire, rp);
            redisTemplate.delete(rpList + rpid);
            redisTemplate.delete(rpUser + rpid);*/
            throw new RespException(RespCode.RP_DONE, null);
        }

        BigDecimal moneyDecimal = BigDecimal.valueOf(Double.valueOf(money));
        addRpMoney(RedPackGrab.builder().redPackId(rpid).toUserId(toUserId).money(moneyDecimal).build());
        redisTemplate.opsForSet().add(rpUser + rpid, String.valueOf(toUserId));
        return moneyDecimal;
    }

    /**
     * 吊销用户当前以前所有token
     *
     * @param userId 用户ID
     */
    public void invokeToken(Long userId) {
        redisTemplate.opsForZSet().add(REDIS_TOKEN_EXPIRE, String.valueOf(userId), new Date().getTime() / 1000);
    }

    public void invokeTokens(List<Long> userIds) {
        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
        userIds.forEach(u -> tuples.add(
                new DefaultTypedTuple<String>(String.valueOf(u), (double) (System.currentTimeMillis() / 1000))));
        redisTemplate.opsForZSet().add(REDIS_TOKEN_EXPIRE, tuples);
    }

    @Transactional
    public void directRp(Long fromUserId, Long toUserId, RedPack redPack) {
        if (!isMoneyGte(fromUserId, redPack.getMoney()))
            throw new RespException(RespCode.NOT_ENOUGH_MONEY, null);
        subMoney(fromUserId, redPack.getMoney());
        redPackMapper.insert(redPack);
        addRpMoney(RedPackGrab.builder()
                .redPackId(redPack.getId())
                .toUserId(toUserId)
                .money(redPack.getMoney())
                .build());
    }

    public Sms sendSms(Sms sms) {
        String smsNum = String.format("%06d", new Random(System.currentTimeMillis()).nextInt(999999));
        Map resp = SmsUtil.sendSms(sms.getPhone(), smsNum);
        sms.setSms(smsNum);
        sms.setCode((String) resp.get("code"));
        sms.setErrorMsg((String) resp.get("errorMsg"));
        sms.setMsgId((String) resp.get("msgId"));
        sms.setTime((String) resp.get("time"));
        if (sms.getCode().equals("0"))   //调用第三方发送短信称
            sms.setValid(0);
        smsRepository.save(sms);

        log.info("send sms: " + JsonUtil.toJson(sms));
        return sms;
    }

    public boolean isSmsValid(String phone, String smsCode) {
        Sms sms = smsMapper.findByPhone(phone);
        if (sms == null){
            System.out.println("sms is null!");
            return false;
        }

        long preTime = sms.getCreateTime().getTime() / 1000;
        long now = new Date().getTime() / 1000;
        if ((now - preTime < 0) || now - preTime > TimeUnit.MINUTES.toSeconds(5)) {
            smsMapper.updateUsed(ImmutableMap.of("id", sms.getId(), "valid", 1));
            return false;
        }

        if (!sms.getSms().equals(smsCode))
            return false;

        smsMapper.updateUsed(ImmutableMap.of("id", sms.getId(), "valid", 1));

        return true;
    }

    public Map<Long, String> userAvatars(List<Long> users) {
        Map<Long, String> res = new HashMap<>();
        List<User> avatars = userMapper.findAvatars(users);
        avatars.forEach(u -> res.put(u.getId(), u.getAvatar()));
        return res;
    }
}
